package models;

import play.mvc.*;
import play.libs.*;
import scala.concurrent.Await;
import scala.concurrent.duration.Duration;
import akka.actor.*;
import static akka.pattern.Patterns.ask;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.util.*;

import models.msgs.*;
import static java.util.concurrent.TimeUnit.*;
import controllers.Application;

public class Table extends UntypedActor {
    
    // Default table.
    static ActorRef defaultTable = Akka.system().actorOf(Props.create(Table.class));
    static int playerNumber = 0;    //actual player number, needed to send cards
    static int playerMove = 1;      //nr of player who can make a move (0 - n-1)
    static int Coins = 1500;
    static int maxBet = 0;
    static int actualRound = 0;
    static int maxPlayers = 2;
    static int Bet = 0;
    static int playersBet[] = new int[maxPlayers]; 
    static int playersCoins[] = new int[maxPlayers];
    static int smallBlind = 3;
    static int bigBlind = smallBlind * 2;
    static int screenCounter;
    static int dealer = 0;
    static int playerWhoDraw = 0;
    static int movesInRound = 0;
    static String currentDeck[];
    static String putDeck[] = new String[0];
	static String players[] = new String[0];
	static String playerCards[] = new String[maxPlayers * 4];
	static String moveName[] = new String[6];
    static boolean nextMove[] = new boolean[6]; 
    static boolean check = false;
    static boolean isBet = false;
    		
    
    // Members of this table.
    
    static Map<String, ActorRef> members = new HashMap<String, ActorRef>();
       
    public static void join(final String name, WebSocket.In<JsonNode> in, WebSocket.Out<JsonNode> out ) throws Exception{

        // Send the Join message to the table
        String result = (String)Await.result(ask(defaultTable,new Join(name), 1000), Duration.create(1, SECONDS));
                
        if("OK".equals(result)) {
        	
            ActorRef player =  Akka.system().actorOf(
                    Props.create(Human.class, name, in, out,  defaultTable ));
            
            members.put(name, player);            
            String text =  "my Actor has been added to Table"; 
            notifyAll(new Info(text,name));        
            playerNumber++;
        }
        
        if (playerNumber == maxPlayers) {
        	System.out.println("All players joined!");  
        	 
        	  for (int i = 0; i < maxPlayers; i++) {
        		  playersCoins[i] = Coins;
        	  }
        	  
        	  moveName[0] = "Check";
        	  moveName[1] = "Raise";
        	  moveName[2] = "Bet";
        	  moveName[3] = "Call";
        	  moveName[4] = "All-in";
        	  moveName[5] = "Fold";
        	  
        	  newGame();    	  
        }      
    }
    
    public static void quit(final String username) throws Exception {
    }
    
       
    public void onReceive(Object message) throws Exception {
        
        if(message instanceof Join) {
            
            Join join = (Join) message;
                        
            if(false){
            	// if username is already taken do sth        	
            }
            else {                
                getSender().tell("OK", getSelf());
            }
        } 
        else if (message instanceof Info)
        {   
        	String text = ((Info) message).getText();
        	String name = ((Info) message).getName();  
        	
        	char mark = text.charAt(0);
        	String action = "";
        	
        	switch (mark) {
    			case '0': { action = "check"; break; }
    			case '1': { action = "raise"; break; }
    			case '2': { action = "bet"; break; }
    			case '3': { action = "call"; break; }
    			case '4': { action = "all-in"; break; }
    			case '5': { action = "fold"; break; }
    			case '6': { action = "change"; break; }
    			default: { action = "error"; break; }
        	}
        	
        	String text2 = "I've played "+action;
        	notifyAll(new Info(text2, name));
        	
        	if (action.equals("check")) {
        		
        		if (nextMove[0] == true) {
        			notifyAll(new Info(name+"'s Coins: "+(playersCoins[playerMove] - playersBet[playerMove])+
        	    			". He's bet: "+playersBet[playerMove]+". Max bet: "+maxBet+". Pot: "+Bet, "Table"));
        		
        			screenCounter++;
        			if (movesInRound >= maxPlayers) {
        				check = true;
        			}		
        			whoIsNext();
        		}
        		else {
        			notifyAll(new Info(name+" - wrong action. Choose again.", "Table"));
        		}
        		
        	}
        	else if (action.equals("raise")) {
        		
        		if (nextMove[1] == true) {
	        		String tmp = text.substring(1); 
	        		maxBet += Integer.parseInt(tmp);
	        		playersBet[playerMove] = maxBet;
	        		whatIsBet();
	        		notifyAll(new Info( name+"'s Coins: "+(playersCoins[playerMove] - playersBet[playerMove])+
        	    			". He's bet: "+playersBet[playerMove]+". Max bet: "+maxBet+". Pot: "+Bet, "Table"));
	        		
	        		screenCounter++;
	        		whoIsNext();
        		}
        		else {
        			notifyAll(new Info(name+" - wrong action. Choose again.", "Table"));
        		}
        	}
        	else if (action.equals("bet")) {
        		
        		if (nextMove[2] == true) {
	        		String tmp = text.substring(1); 
	        		maxBet += Integer.parseInt(tmp);
	        		playersBet[playerMove] = maxBet;
	        		notifyAll(new Info(name+"'s Coins: "+(playersCoins[playerMove] - playersBet[playerMove])+
        	    			". He's bet: "+playersBet[playerMove]+". Max bet: "+maxBet+". Pot: "+Bet, "Table"));
	        	
	        		screenCounter++;
	        		isBet = false;
	        		whoIsNext();
        		}
        		else {
        			notifyAll(new Info(name+" - wrong action. Choose again.", "Table"));
        		}
        		
        	}
			else if (action.equals("call")) {
				
				if (nextMove[3] == true) {
					playersBet[playerMove] = maxBet;
					whatIsBet();
					notifyAll(new Info(name+"'s Coins: "+(playersCoins[playerMove] - playersBet[playerMove])+
        	    			". He's bet: "+playersBet[playerMove]+". Max bet: "+maxBet+". Pot: "+Bet, "Table"));
					
					screenCounter++;
					whoIsNext();
				}
				else {
					notifyAll(new Info(name+" - wrong action. Choose again.", "Table"));
				}
	
			}
			else if (action.equals("all-in")) {
				
				if (nextMove[3] == true) {
	        		notifyAll(new Info(name+"'s Coins: "+(playersCoins[playerMove] - playersBet[playerMove])+
        	    			". He's bet: "+playersBet[playerMove]+". Max bet: "+maxBet+". Pot: "+Bet, "Table"));
	        	
	        		screenCounter++;
					whoIsNext();
				}
				else {
					notifyAll(new Info(name+" - wrong action. Choose again.", "Table"));
				}
				
			}
			else if (action.equals("fold")) {
        		
        		screenCounter++;      	
				whoIsNext();
				
			}
			else if (action.equals("change")) {
				
				playerWhoDraw++;
				String cardsToChange[] = new String[text.length() - 1];
				for (int i = 1; i < text.length(); i++) {
					cardsToChange[i - 1] = ""+ text.charAt(i);
				}
				
				drawCards(playerMove, cardsToChange);
				
				if (playerWhoDraw == maxPlayers) {
					cardsNotify();
					screenCounter = 0;
					newRound();
				}
				else {
					playerMove++;
		        	playerMove = playerMove % maxPlayers;
		    		Object msg = new Info(name+" draws cards.", "Table");    	
		        	Object msg2 = new Info("It's your turn to draw a cards!", "Table");
		        	
		        	int k = 0;
		    		for (ActorRef member : members.values())  {
		        		if (k == playerMove) {
		        			 member.tell(msg2, defaultTable);
		        		}
		        		else {
		        			 member.tell(msg, defaultTable);
		        		}
		        		k++;
		            }
		    		screenCounter++; 
				}
			}
			else if (action.equals("error")) {
				
			}
        }  
        else if(message instanceof Quit)  {            
           
            String name = ((Quit)message).getName();            
            members.remove(name);
            String text = "has left the game";            		
            notifyAll(new Info(text, name));
                 
        } 
        else {
            unhandled(message);
        }
        
        if (screenCounter >= 5) {
        	screenCounter = 0;
        	cardsNotify();
        }
        
    }
    
    static public void whatIsBet() {
    	Bet = 0;
    	for (int i = 0; i < maxPlayers; i++) {
    		Bet = Bet + playersBet[i];
    	}
    }
    
        
    static public void notifyAll(Object msg) {
        for (ActorRef member : members.values()) {
            member.tell(msg, defaultTable);
        }
    }
    
    static public void notifyThis(ActorRef member, Object msg) {
         member.tell(msg, defaultTable);
    }
    
    static public void newGame() {
    	
      	notifyAll(new Info("Game started!", "Table"));
    	currentDeck = shuffleDeck(newDeckOfCards());
    	handOfCards();
    	String cards = "";
    	int k = 0;
    	
    	for (ActorRef member : members.values()) {  
    		
    		playersBet[k] = 0;
    		for (int i = 0; i < 4; i++) {
          		cards += playerCards[k*4 + i] + " | ";
          	}
    		  
    		notifyThis(member, new Info(cards, "Table"));

    		cards = "";
    		k++;
        }
    	
    	playerMove = (dealer + 1) % maxPlayers;
    	maxBet = smallBlind;
		playersBet[playerMove] = maxBet;
		actualRound = 1;
		
		notifyAll(new Info("player "+playerMove+" has smallBlind. He have "+(playersCoins[playerMove] - playersBet[playerMove])+" coins. "
				+"Max bet is "+maxBet+". Actual bet is "+Bet, "Table"));
	
		
		playerMove++;
    	playerMove = playerMove % maxPlayers;
    	
    	maxBet = bigBlind;
    	playersBet[playerMove] = maxBet;
    	
    	whatIsBet();
		notifyAll(new Info("player "+playerMove+" has bigBlind. He have "+(playersCoins[playerMove] - playersBet[playerMove])+
				" coins. Max bet is "+maxBet+". Actual bet is "+Bet, "Table"));
		screenCounter = 2;
		nextMove[1] = true;
		nextMove[3] = true;
		nextMove[4] = true;
		nextMove[5] = true;
		
		dealer = (playerMove + 1) % maxPlayers;

  	  	whoIsNext();   
    	
    }
    
    /* Refreshing player cards on screen */
    static public void cardsNotify() { 
    	
    	int k = 0;
    	for (ActorRef member : members.values()) { 
    		String cards = "";
    		for (int i = 0; i < 4; i++) {
    			cards += playerCards[k*4 + i] + " | ";
    		}
    		notifyThis(member, new Info(cards, "Table"));
    		k++;
        }
    	
    }
    
    static public void whoIsNext() {
    	
    	int test = 0;
    	/* Sprawdzenie czy wszyscy maja taki sam bet */
    	System.out.println("check "+check);
    	if (check == true) {
    		for (int i = 0; i < maxPlayers - 1; i++) {
    			if (playersBet[i] == playersBet[i+1]) {
    				test++;
    			}
    		}
    	}  	
    	System.out.println(test +" "+ maxPlayers);
    	
    	/* Sprawdzenie czy powinny byc nastepnie wymieniane karty */
    	if (test != (maxPlayers - 1)) {
    		check = false;
        	playerMove++;
        	playerMove = playerMove % maxPlayers;
        	
        	if (isBet == true) {
        		nextMove[0] = true;  nextMove[1] = false;  nextMove[2] = true; 
        		nextMove[3] = false;  nextMove[4] = true;  nextMove[5] = true;
        	}
        	else if (playersBet[playerMove] == maxBet) {
        		nextMove[0] = true;  nextMove[1] = true;  nextMove[2] = false; 
        		nextMove[3] = false;  nextMove[4] = true;  nextMove[5] = true;
        	}
        	else if (playersBet[playerMove] < maxBet) {
        		nextMove[0] = false;  nextMove[1] = true;  nextMove[2] = false; 
        		nextMove[3] = true;  nextMove[4] = true;  nextMove[5] = true;
        	}
        	
        	String possibleMoves = "Possible moves";
        	for (int i = 0; i < 6; i++) {
        		if (nextMove[i] == true) {
        			possibleMoves = possibleMoves + ". " + moveName[i];
        		}
        	}
        	
        	int k = 0;
        	
        	Object msg = new Info("It's your turn! "+possibleMoves ,"Table");    	
        	Object msg2 = new Info("It's player "+playerMove+" move!" ,"Table");
        	
        	for (ActorRef member : members.values())  {
        		if (k == playerMove) {
        			 member.tell(msg, defaultTable);
        		}
        		else {
        			 member.tell(msg2, defaultTable);
        		}
        		k++;
            }
    	}
    	else {
    		if (actualRound < 4) {
    			playerMove++;
            	playerMove = playerMove % maxPlayers;
        		Object msg = new Info("End of Round! Player "+playerMove+" draws cards.", "Table");    	
            	Object msg2 = new Info("End of Round! It's your turn to draw a cards!", "Table");
            	
            	int k = 0;
        		for (ActorRef member : members.values())  {
            		if (k == playerMove) {
            			 member.tell(msg2, defaultTable);
            		}
            		else {
            			 member.tell(msg, defaultTable);
            		}
            		k++;
                }
        		for (int i = 0; i < 6; i++) {
        			nextMove[i] = false;
        		}
        		check = false;
    		}
    		else {
    			notifyAll(new Info("End of game!", "Table"));
    			chooseWinner();
    		}
    		
    	}
    	
    	movesInRound++;
		screenCounter++; 
    }
    
    static void newRound() {
    	actualRound++;
    	notifyAll(new Info("Round "+actualRound+" started!", "Table"));
        screenCounter++;
        playerMove = dealer;
        isBet = true;
        movesInRound = 0;
        playerWhoDraw = 0;
        whoIsNext(); 
    }
    
    static String[] newDeckOfCards() {
		String[] newDeck = new String[52];
		
		int[] numbers = new int[]{1,2,3,4,5,6,7,8,9,10,11,12,13};
		String[] colors = new String[]{"Red-Heart", "Red-Diamond", "Black-Spade", "Black-Club"};
		
		int index = 0;
		for (int i = 0; i < numbers.length; i++) {
			for (int j = 0; j < colors.length; j++) {
				newDeck[index] = numbers[i] + "-" + colors[j];
				index += 1;
			}
		}
		return newDeck;
	}
    
    
    private static String[] shuffleDeck(String[] oldDeck) {
		Random random = new Random();
		
	    for (int i = oldDeck.length - 1; i > 0; i--) {
	      int index = random.nextInt(i + 1);
	     
	      String oldIndexValue = oldDeck[index];
	      
	      oldDeck[index] = oldDeck[i];
	      oldDeck[i] = oldIndexValue;
	    }
	    
	    return oldDeck;
	}
    
    public static void drawCards(int playerId, String[] tab) {
		
		for (int i = 0; i < tab.length; i++) {
			int index = Integer.parseInt(tab[i]) + (playerId)*4;
			
			if (currentDeck.length == 0) {
				currentDeck = shuffleDeck(putDeck);
				putDeck = new String[0];
			}
			
			// Zapamietaj stara karte
			// Pobierz nowa karte z nowej talii
			String oldCard = playerCards[index];
			String newCard = currentDeck[0];
			
			// Usun pobrana karte z talii
			// Wymien karte na nowa
			currentDeck[0] = null;
			playerCards[index] = newCard;
			
			// Usun puste miejsce po pobranej karcie z obecnej talii
			// Dopisz wymieniona karte do odlozonej talii
			sliceAllNulls(currentDeck);
			extendPutDeck(putDeck, oldCard);
		}
	}
    
    public static void handOfCards() {
		for (int i = 0; i < maxPlayers * 4; i++) {
			playerCards[i] = currentDeck[i];
			currentDeck[i] = null;
		}
		
		sliceAllNulls(currentDeck);
	}
    
    
    public static void sliceAllNulls(String[] deck) {
		int index = 0;
		int newLength = 0;
		String[] newDeck = null;
		
		for (int i = 0; i < deck.length; i++) {
			if (deck[i] != null)
				newLength += 1;
		}
		
		newDeck = new String[newLength];
		
		for (int i = 0; i < deck.length; i++) {
			if (deck[i] != null) {
				newDeck[index] = deck[i];
				index += 1;
			}
		}
		
		currentDeck = new String[newLength];
		currentDeck = newDeck;
	}
    
    
    
	public static void extendPutDeck(String[] deck, String card) {
		String[] newDeck = new String[deck.length+1];
		
		for (int i = 0; i < deck.length; i++)
			newDeck[i] = deck[i];
		
		newDeck[deck.length] = card;
		
		putDeck = new String[newDeck.length];
		putDeck = newDeck;
	}
	
	public static void chooseWinner() {
		String result = "";
		String[] allHands = new String[maxPlayers];
		
		for (int i = 0; i < maxPlayers; i++) {
			String[][] tmp = new String[4][3];
						
				for (int j = 0; j < 4; j++) {
					tmp[j] = playerCards[i*4+j].split("-");
				}
			
				allHands[i] = encodeHand(organizeCards(tmp));

			System.out.println(" player " + i + "'s hand code: " + allHands[i]);
		}
		
		String cmp = allHands[0];
		int dupl = 0;
		int winnerID = 1;
		
		
		for (int i = 0; i+1 < allHands.length; i++) {
			if (cmp.compareTo(allHands[i+1]) == 0) {
				dupl += 1;
			} 
			else if (cmp.compareTo(allHands[i+1]) > 0) {
				cmp = allHands[i+1];
				winnerID = i+2;
				dupl = 0;
			}
		}
		
		winnerID--;
		
		System.out.println("Winner ID "+winnerID);

		if (dupl == 0) {
			notifyAll(new Info("Winner is player "+winnerID, "Table"));
			for (int i = 0; i < maxPlayers; i++) {
				if (i == winnerID) {
					playersCoins[i] += Bet; 
				}
				else {
					playersCoins[i] -= playersBet[i];
				}
			}
			
				
		} 
		else {
			notifyAll(new Info("Game result: TIE! ", "Table"));
			
			for (int i = 0; i < maxPlayers; i++) {
				playersCoins[i] -= playersBet[i];
			}
		}
	}
	
	
	
	static String encodeHand(int[] cardsValues) {
		String[] chars = new String[]{"x","a","b","c","d","e","f","g","h","i","j","k","l","m","n"};
		String handCode = "";
		
		for (int i = cardsValues.length-1; i >= 0 ; i--) {
			handCode += chars[cardsValues[i]];
		}
		
		return handCode;
	}
	
	
	static int[] organizeCards(String[][] array) {
		String[][] sortedCards = sortCards(array);
		String[][] cards = sortedCards.clone();
		int[][] hands = new int[2][4];


		for (int k = 0; k <= 1; k++) {
			String values = "";
			String shapes = "";
			int[] numeric = new int[4];
			
			for (int i = 0; i <= 3; i++) {
				int min = 100;
				int minIndex = -1;
				
				for (int j = 0; j <= 3; j++) {
					if (cards[j][0] == null)
						continue;
					
					if (values.contains("["+cards[j][0]+"]") || shapes.contains(cards[j][2])) {
						cards[j] = new String[3];
					} 
					else if (Integer.parseInt(cards[j][0]) < min) {
						min = Integer.parseInt(cards[j][0]);
						minIndex = j;
					}
				}
				
				if (minIndex == -1) {
					values += "[0]";
					numeric[i] = 14;
					continue;
				}
				
				values += "["+cards[minIndex][0]+"]";
				numeric[i] = Integer.parseInt(cards[minIndex][0]);
				shapes += cards[minIndex][2];
				cards[minIndex] = new String[3];
			}
			
			
			for (int l = 0; l <= 3; l++) {
				cards[l] = sortedCards[3-l];
			}
			
			hands[k] = numeric;
		}
		
		int[] cardsValues = hands[0];
		
		for (int i = 3; i >= 0; i--) {
			if (hands[0][i] > hands[1][i])
				cardsValues = hands[1];
				break;
		}
		
		return cardsValues;
	} 
	
	
	static String[][] sortCards(String[][] unsorted) {
		String[][] sorted = new String[4][3];
		int j = 0, index = 0;
		
		for (int i = 0; i < unsorted.length; i++) {
			int min = 15;
			
			for (j = 0; j < unsorted.length; j++) {
				if (unsorted[j][0] != null) {
					if (Integer.parseInt(unsorted[j][0]) < min) {
						min = Integer.parseInt(unsorted[j][0]);
						index = j;
					}
				}
			}
			
			sorted[i] = unsorted[index];
			unsorted[index] = new String[3];
		}
		
		return sorted;
	}
    
}

